package dse.messages;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Hashtable;

import dse.BloomFilter;
import dse.Cluster;
import dse.DataObject;
import dse.Network;
import dse.Node;
import dse.modules.Assigner;
import dse.Pair;
//import dse.modules.eln.*;

class ComputeClusterRing{
	double distance;
	LinkedList<Cluster> clusterRing;
	
	public ComputeClusterRing(){
		distance = Double.MAX_VALUE;
		clusterRing = new LinkedList<Cluster>();
	}
	
	public LinkedList<Cluster> getClusterRing(){
		return this.clusterRing;
	}
	
	public void compute(LinkedList<Cluster> list, LinkedList<Cluster> removed){
		int length = list.size();
		if (length == 1) {
			removed.add(list.get(0));
			double dist = 0;
			for (int i=0; i<removed.size(); i++) {
				int j= (i==removed.size()-1?0:i+1);
				dist += removed.get(j).distanceTo(removed.get(i));
			}
			if(distance > dist){
				distance = dist;
				clusterRing.clear();
				clusterRing.addAll(removed);
			}
		} 
		else {
			for (int i = 0; i < length; i++) {                        
				LinkedList<Cluster> temp = new LinkedList<Cluster>();
				temp.addAll(list);            
				temp.remove(i);
				LinkedList<Cluster> holder = new LinkedList<Cluster>();
				holder.addAll(removed);    
				holder.add(list.get(i));
				compute(temp, holder);
			}
		}
	}
}

public class MessageUtil {
	
	private MessageUtil () {/**/}
	
	public static LinkedList<Cluster> getClustersThatMayHaveDataObjectV2(
			Network network,
			Node subject,
			DataObject dataObject,
			Assigner assigner
			)
	{
		//	OK, now lets see what our cluster knows!
		LinkedList<Cluster> clusters = new LinkedList<Cluster> ();
		
		final Cluster home = 
			network.lookupCluster(subject.clusterId());
		
		
		// Well we should always be able to hash the appropriate cluster for the
		// data object
		Iterable<Cluster> designatedHosts = assigner.assign(
				dataObject, network.clusters(), 
				network.maxClusterId()
				);
		for (Cluster c  : designatedHosts)
			clusters.add(c);
		
		// However we should also check our bloom filter...
		/*Map<Cluster,BloomFilter<DataObject>> bbfs = 
			home.neighborhoodBloomFilters();
		
		for (Cluster c : bbfs.keySet()) {
			BloomFilter<DataObject> bf = bbfs.get(c);
			
			if (bf.query(dataObject))
				clusters.add(c);
		}
		
		if(home.bloomFilter().query(dataObject))
			clusters.add(home);
		*/
		// We need to store the list by those that are closest to the requester
		Collections.sort(clusters, new Comparator<Cluster>() {
			public int compare (Cluster a, Cluster b) {
				return (int)(home.distanceTo(a) - home.distanceTo(b));
			}
			});
		
		return clusters;
	}
	
	public static LinkedList<Cluster> getClustersThatMayHaveDataObject(
			Network network,
			Node subject,
			DataObject dataObject,
			Assigner assigner
			)
	{
		//	OK, now lets see what our cluster knows!
		LinkedList<Cluster> clusters = new LinkedList<Cluster> ();
		
		final Cluster home = 
			network.lookupCluster(subject.clusterId());
		
		
		// Well we should always be able to hash the appropriate cluster for the
		// data object
		Iterable<Cluster> designatedHosts = assigner.assign(
				dataObject, network.clusters(), 
				network.maxClusterId()
				);
		for (Cluster c  : designatedHosts)
			clusters.add(c);
		
		// However we should also check our bloom filter...
		Map<Cluster,BloomFilter<DataObject>> bbfs = 
			home.neighborhoodBloomFilters();
		
		for (Cluster c : bbfs.keySet()) {
			BloomFilter<DataObject> bf = bbfs.get(c);
			
			if (bf.query(dataObject))
				clusters.add(c);
		}
		
		// We need to store the list by those that are closest to the requester
		Collections.sort(clusters, new Comparator<Cluster>() {
			public int compare (Cluster a, Cluster b) {
				return (int)(home.distanceTo(a) - home.distanceTo(b));
			}
			});
		
		return clusters;
	}
	
	public static LinkedList<Pair<Node, Integer>> getNodesThatMayHaveDataObjectShare(
			Network network,
			Node subject,
			DataObject dataObject,
			Assigner assigner
			)
	{
		//	OK, now lets see what our node knows!
		LinkedList<Pair<Node, Integer>> nodes = new LinkedList<Pair<Node, Integer>>();
		final Node homeNode = subject;
		Hashtable<Node, Integer> validNodes = assigner.hostedShares(network, dataObject);
		// Well we should always be able to hash the appropriate nodes for the
		// data object
		for (Node n : validNodes.keySet())
		{
			nodes.add(new Pair<Node, Integer>(n, validNodes.get(n)));		
		}
		
		// We need to store the list by those that are closest to the requester
		Collections.sort(nodes, new Comparator<Pair<Node, Integer>>() {
			public int compare (Pair<Node, Integer> a, Pair<Node, Integer> b) {
				return (int)(homeNode.distanceTo(a.first) - homeNode.distanceTo(b.first));
			}
			});
		
		return nodes;
	}
	
	public static LinkedList<Node> getNodesByDht(
			Network network,
			Node subject,
			DataObject dataObject,
			Assigner assigner
			)
	{
		//	OK, now lets see what our node knows!
		LinkedList<Node> nodes = new LinkedList<Node>();
		final Node homeNode = subject;
		// Well we should always be able to hash the appropriate nodes for the
		// data object
		for (int i=1; i<=dataObject.shareCount(); i++)
		{
			Node n = assigner.assign(dataObject, i, network);
			nodes.add(n);
		}
		
		// We need to store the list by those that are closest to the requester
		Collections.sort(nodes, new Comparator<Node>() {
			public int compare (Node a, Node b) {
				return (int)(homeNode.distanceTo(a) - homeNode.distanceTo(b));
			}
			});
		
		return nodes;
	}
	
	public static LinkedList<Cluster> getClustersRing(
			Network network,
			Cluster home,
			DataObject dataObject,
			Assigner assigner
			)
	{
		//	OK, now lets see what our cluster knows!
		LinkedList<Cluster> clusters = new LinkedList<Cluster> ();
				
		// Well we should always be able to hash the appropriate cluster for the
		// data object
		Iterable<Cluster> designatedHosts = assigner.assign(
				dataObject, network.clusters(), network.maxClusterId());
		
		for (Cluster c  : designatedHosts)
			clusters.add(c);
		
		// However we should also check our bloom filter...		
		
		// We need to store the list by those that are closest to the requester
		/*Collections.sort(clusters, new Comparator<Cluster>() {
			public int compare (Cluster a, Cluster b) {
				EarthLikeNetworkCluster l = (EarthLikeNetworkCluster) a;
				EarthLikeNetworkCluster r = (EarthLikeNetworkCluster) b;
				if(l.X() < r.X())
					return -1;
				else if(l.X() > r.X())
					return 1;
				else if(l.Y() < r.Y())
					return -1;
				else if(l.Y() > r.Y())
					return 1;
				return 0;
			}
			});*/
		ComputeClusterRing ccr = new ComputeClusterRing();
		ccr.compute(clusters, new LinkedList<Cluster>());
		clusters.clear();
		clusters.addAll(ccr.getClusterRing());
		Cluster cl = clusters.getFirst();
		while(cl.id() != home.id())
		{
			cl = clusters.removeFirst();
			clusters.addLast(cl);
		}
		clusters.removeFirst();
		return clusters;
	}
	
	public static LinkedList<Node> getResidenceNodesInCluster(
			Cluster cluster,
			Node subject,
			DataObject dataObject,
			Assigner assigner)
	{
		LinkedList<Node> nodes = new LinkedList<Node>();
		final Node homeNode = subject;
		// Well we should always be able to hash the appropriate nodes for the
		// data object
		for (int i=1; i<=dataObject.shareCount(); i++)
		{
			Node n = assigner.assign(dataObject, i, cluster);
			nodes.add(n);
		}
		
		// We need to store the list by those that are closest to the requester
		Collections.sort(nodes, new Comparator<Node>() {
			public int compare (Node a, Node b) {
				return (int)(homeNode.distanceTo(a) - homeNode.distanceTo(b));
			}
			});
		
		return nodes;
	}
	
	public static LinkedList<Node> getVersionServersByDht(
			Network network,
			DataObject dataObject,
			int num,
			Assigner assigner
			)
	{
		//	OK, now lets see what our node knows!
		LinkedList<Node> nodes = new LinkedList<Node>();
		// Well we should always be able to hash the appropriate nodes for the
		// data object
		for (int i=1; i<=2*num+1; i++)
		{
			Node n = assigner.assign(dataObject, i, network);
			nodes.add(n);
		}
		
		return nodes;
	}
}